package com.blyat.xsoft.kernel.component.extension;

import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

/**
 * @Auther: syh
 * @Date: 2020/9/28
 * @Description:
 */
public class ExtensionLoader<T> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionLoader.class);

    private static final String EHC_FILE_PATH = "META-INF/ehc/";

    private String interfaceName;

    private Map<String, ExtensionClass<T>> alias2ExtensionClass;
    private final ConcurrentMap<String, Holder<T>> cachedInstances = new ConcurrentHashMap<>();

    public ExtensionLoader(Class<T> interfaceClass) {
        this.interfaceName = interfaceClass.getName();
        this.alias2ExtensionClass = new ConcurrentHashMap<>();
        // 此处只指定了一个 spi 文件存储的路径
        loadFromFile(EHC_FILE_PATH + "internal/");
        // loadFromFile(EHC_FILE_PATH + "other/");
    }

    private void loadFromFile(String spiConfigPath) {
        String spiFile = spiConfigPath + interfaceName;
        try {
            ClassLoader classLoader = this.getClass().getClassLoader();
            loadFromClassLoader(classLoader, spiFile);
        } catch (Exception e) {
            LOGGER.error("load file {} error, ", spiFile, e);
        }
    }

    private void loadFromClassLoader(ClassLoader classLoader, String spiFile) throws IOException {

        Enumeration<URL> urls = classLoader != null ? classLoader.getResources(spiFile) : ClassLoader.getSystemResources(spiFile);
        if (urls == null) {
            return;
        }
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    readLine(line);
                }
            } catch (Exception e) { // 文件需要整体失败，不能单行失败
                LOGGER.error("load {} fail，", spiFile, e);
            }
        }
    }

    private void readLine(String line) throws ClassNotFoundException {
        String[] aliasAndClassName = line.split("=");

        if (aliasAndClassName == null || aliasAndClassName.length != 2) {
            return;
        }

        String alias = aliasAndClassName[0].trim();
        String className = aliasAndClassName[1].trim();
        Class<?> clazz = Class.forName(className, false, this.getClass().getClassLoader());

        // 必须具有扩展注解
        Extension extension = clazz.getAnnotation(Extension.class);
        if (extension == null) {
            LOGGER.error("{} need @Extension", className);
            return;
        }

        ExtensionClass<T> extensionClass = new ExtensionClass<>((Class<? extends T>) clazz);
        alias2ExtensionClass.putIfAbsent(alias, extensionClass);
    }

    public T getExtension(String alias) {
        Function<ExtensionClass<T>, T> fun = (clazz) -> clazz.getExtInstance();
        return getCachedExtension(alias, fun);
    }

    public T getExtension(String alias, ApplicationContext context) {
        Function<ExtensionClass<T>, T> fun = (clazz) -> context == null ? clazz.getExtInstance() : clazz.getExtInstance(context);
        return getCachedExtension(alias, fun);
    }

    public T getExtension(String alias, Class[] argTypes, Object[] args) {
        Function<ExtensionClass<T>, T> fun = (clazz) -> clazz.getExtInstance(argTypes, args);
        return getCachedExtension(alias, fun);
    }

    private T getCachedExtension(String alias, Function<ExtensionClass<T>, T> fun) {
        Holder<T> holder = cachedInstances.get(alias);
        if (holder == null) {
            cachedInstances.putIfAbsent(alias, new Holder<>());
            holder = cachedInstances.get(alias);
        }
        T instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    ExtensionClass<T> extensionClass = alias2ExtensionClass.get(alias);
                    if (extensionClass == null) {
                        throw new RuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!");
                    }
                    instance = fun.apply(extensionClass);
                    holder.set(instance);
                }
            }
        }

        return instance;
    }

    public List<T> getExtensions() {
        List<T> extensionClasses = Lists.newArrayListWithExpectedSize(alias2ExtensionClass.size());
        for (ExtensionClass<T> extensionClass : alias2ExtensionClass.values()) {
            extensionClasses.add(extensionClass.getExtInstance());
        }

        if (extensionClasses == null || extensionClasses.isEmpty()) {
            throw new RuntimeException("Not found extensions of " + interfaceName + "\"!");
        }

        return extensionClasses;
    }
}
